/*******************************************************************************
 *
 * File Name:          RequestProcessor.java
 *
 * Created:            2006-9-30 7:24:33
 *
 * Original Author:    yu.peng
 *
 *------------------------------------------------------------------------------
 * MODIFICATION HISTORY
 *------------------------------------------------------------------------------
 * WHEN                WHO                 WHY
 * 2006-9-30 7:24:33   yu.peng             new
 * 2007-8-16 21:47:33  yu.peng             因为增加了的RemoteUserRequestWrapper
 *                                         提供了isUserInRole方法,
 *                                         所以不需要重写processRoles
 *------------------------------------------------------------------------------
 *
 ******************************************************************************/
/*******************/
/* PACKAGE SECTION */
/*******************/

package org.njo.webapp.root.action;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.GenericValidator;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.tiles.TilesRequestProcessor;
import org.njo.webapp.root.config.ControllerConfig;
import org.njo.webapp.root.servlet.wrapper.RemoteUserRequestWrapper;

/**
 * 这个类作为struts的请求处理器.
 * 完成了一下功能:
 * 1,覆盖了processRoles方法,修改了对角色的判断方法.
 *
 * @author yu.peng
 * @version 0.01
 */
public class RequestProcessor extends TilesRequestProcessor {

    public static Log logger = LogFactory.getLog(RequestProcessor.class);

    /**
     * 为了方便EL使用,这里定义了简单的'message'作为,消息对象存放在request的key.
     * TODO:这个常数是否应该在这里定义,需要研究.暂时先放在这里
     */
    public final static String MESSAGE_KEY = "message";

    /**
     * 当用户请求没有权限的页面时,会重镜像到login之类的页面,
     * 为了使用户能够在重新login,取得新的角色时,自动重镜像
     * 刚刚请求的页面而定义的.
     * 当用户请求没有权限的页面时,会在session中保存这个请求的url.
     * TODO:这个常数是否应该在这里定义,需要研究.暂时先放在这里
     */
    public final static String NOTAUTHORIZED_REQUESTED_URL_SESSION_KEY = "notauthorized_requested_url";

    /**
     * 用于保存缺省的跳转页面
     */
    public static String DEFAULT_REDIRECT_PAGE = "/index.tiles";

    /**
     * Login页面.
     */
    public static String DEFAULT_LOGIN_PAGE = "/login.tiles";

    /**
     * 覆盖初始化方法.
     * 目的:取得配置文件中的配置的Login页面和重镜像页面.
     * @param actionServlet
     * @param moduleConfig
     * @throws javax.servlet.ServletException
     */
    public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws javax.servlet.ServletException {
        super.init(actionServlet, moduleConfig);
        if (moduleConfig!=null) {
            ControllerConfig controllerConfig = (ControllerConfig)moduleConfig.getControllerConfig();
            if (controllerConfig!=null) {
                if (!GenericValidator.isBlankOrNull(controllerConfig.getDefaultLoginPage())) {
                    DEFAULT_LOGIN_PAGE = controllerConfig.getDefaultLoginPage();
                }
                if (!GenericValidator.isBlankOrNull(controllerConfig.getDefaultRedirectPage())) {
                    DEFAULT_REDIRECT_PAGE = controllerConfig.getDefaultRedirectPage();
                }
                if (logger.isDebugEnabled()) {
                    logger.debug(" DEFAULT_LOGIN_PAGE=" + DEFAULT_LOGIN_PAGE +
                            ", DEFAULT_REDIRECT_PAGE=" + DEFAULT_REDIRECT_PAGE);
                }
            }
        }
    }

    /**
     * 覆盖父类的process方法,为Request提供包装,在执行父类的process方法.
     */
    public void process(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        // 包装request类
        RemoteUserRequestWrapper wrapper = new RemoteUserRequestWrapper(request);
        super.process(wrapper, response);
    }

    /**
     * <p>If this action is protected by security roles, make sure that the
     * current user possesses at least one of them.  Return <code>true</code>
     * to continue normal processing, or <code>false</code> if an appropriate
     * response has been created and processing should terminate.</p>
     * <p/>
     * 覆盖父类的 processRoles 方法.修改了判定用户是否拥有指定角色的方法.
     *
     * @param request  The servlet request we are processing
     * @param response The servlet response we are creating
     * @param mapping  The mapping we are using
     * @throws IOException      if an input/output error occurs
     * @throws ServletException if a servlet exception occurs
     */
    protected boolean processRoles(HttpServletRequest request,
                                   HttpServletResponse response,
                                   ActionMapping mapping) throws IOException, ServletException {

        // Is this action protected by role requirements?
        String roles[] = mapping.getRoleNames();
        if ((roles == null) || (roles.length < 1)) {
            return true;
        } else {
            // 取得用户信息
            // Check the current user against the list of required roles
            for (int i = 0; i < roles.length; i++) {
                if (request.isUserInRole(roles[i])) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(" User '" + request.getRemoteUser() + "' has role '" + roles[i] + "', granting access");
                    }
                    return true;
                }
            }

            // The current user is not authorized for this action
            if (logger.isDebugEnabled()) {
                logger.debug(" User '" + request.getRemoteUser() + "' does not have any required role, denying access");
            }

            String path = getPath(request, response);
            if (path != null) {
                request.setAttribute(NOTAUTHORIZED_REQUESTED_URL_SESSION_KEY, path);
            }

            // 如果没有没有权限,则转发到login页面
            request.getRequestDispatcher(RequestProcessor.DEFAULT_LOGIN_PAGE).forward(request, response);
            // TODO:好像还有一种方法可以重镜像到指定页面,
            //      就像下边的代码似的,发送一个401的错误.
            //      通过web.xml指定401错误的页面,
            //      但是,这样做会时session重置,导致session信息不可用
            //           如果能够解决这个问题,还是使用这个方法比较灵活
//                response.sendError(HttpServletResponse.SC_FORBIDDEN);
//                response.sendError(
//                        HttpServletResponse.SC_FORBIDDEN,
//                        getInternal().getMessage("notAuthorized", mapping.getPath()));
            return false;
        }
    }

    
    
    public static final char ARRAY_FIELD_DELIM = '$';
    /**
     * 在struts映射参数之后,检查是否有数组(列表)类型,并重新映射.
     * 
     * 注意:这个方法只能映射非上传类型的提交.
     * 
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param form The ActionForm instance we are populating
     * @param mapping The ActionMapping we are using
     *
     * @exception ServletException if thrown by RequestUtils.populate()
     */
    protected void processPopulate(HttpServletRequest request,
            HttpServletResponse response, ActionForm form, ActionMapping mapping)
            throws ServletException {
        super.processPopulate(request, response, form, mapping);
        populate(form, mapping.getPrefix(), mapping.getSuffix(), request);
    }

    /**
     * 映射列表类型数据.
     * 
     * @param bean
     * @param prefix
     * @param suffix
     * @param request
     * @throws ServletException
     */
    private void populate(Object bean, String prefix, String suffix,
            HttpServletRequest request) throws ServletException {
        String contentType = request.getContentType();
        String method = request.getMethod();
        boolean isMultipart = (contentType != null)
                && (contentType.startsWith("multipart/form-data"))
                && (method.equalsIgnoreCase("POST"));
        if (isMultipart) {
            return;
        }
        // Iterator of parameter names
        Enumeration names = request.getParameterNames();

        // 取得并保存相关的参数值(数组[列表]类型)
        Map<String, List> properties = new HashMap<String, List>();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            String stripped = name;
            if (prefix != null) {
                if (!stripped.startsWith(prefix)) {
                    continue;
                }
                stripped = stripped.substring(prefix.length());
            }
            if (suffix != null) {
                if (!stripped.endsWith(suffix)) {
                    continue;
                }
                stripped = stripped.substring(0, stripped.length()
                        - suffix.length());
            }

            // Populate parameters, except "standard" struts attributes
            // such as 'org.apache.struts.action.CANCEL'
            if (!(stripped.startsWith("org.apache.struts."))) {
                int delim = stripped.indexOf(ARRAY_FIELD_DELIM);
                // 判定如果是数组(列表)类型的参数时
                if (delim >= 0) {
                    String[] parameterValue = request.getParameterValues(name);
                    // request参数名(字段名)
                    String arr_attribute_name = stripped.substring(0, delim);
                    // 转换后的参数名
                    String property_key = stripped.substring(delim + 1);
                    // 参数值列表
                    List valueList = (List)properties.get(arr_attribute_name);
                    // 指定参数名的参数值列表不存在时,创建新的列表并保存在参数Map中
                    if (valueList == null) {
                        valueList = new ArrayList();
                        properties.put(arr_attribute_name, valueList);
                    }
                    // 将参数值保存在参数Map中
                    for (int i0 = 0; i0 < parameterValue.length; i0++) {
                        if (i0 >= valueList.size()) {
                            valueList.add(new HashMap());
                        }
                        Map innerMap = (Map) valueList.get(i0);
                        innerMap.put(property_key, parameterValue[i0]);
                    }
                }
            }
        }

        // Set the corresponding properties of our bean
        try {
            BeanUtils.populate(bean, properties);
        } catch (Exception e) {
            throw new ServletException("BeanUtils.populate", e);
        }
    }


    /**
     * 取得用户请求的路径. 这个方法是参考父类的 getPath 方法写的.
     * 
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    private String getPath(HttpServletRequest request,
                           HttpServletResponse response) throws IOException {
        // For prefix matching, match on the path info (if any)
        String path = (String) request.getAttribute(INCLUDE_PATH_INFO);
        if (path == null) {
            path = request.getPathInfo();
        }
        if ((path != null) && (path.length() > 0)) {
            return (path);
        }
        // For extension matching, strip the module prefix and extension
        path = (String) request.getAttribute(INCLUDE_SERVLET_PATH);
        if (path == null) {
            path = request.getServletPath();
        }
        String prefix = moduleConfig.getPrefix();
        if (!path.startsWith(prefix)) {
            String msg = getInternal().getMessage("processPath", request.getRequestURI());
            logger.error(msg);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
            return null;
        }
        path = path.substring(prefix.length());
        String queryString = request.getQueryString();
        if (queryString!=null) {
            path = path + "?" + queryString;
        }
        return (path);
    }

    /**
     *
     * @param request
     * @param message
     */
    public static void addMsg(HttpServletRequest request, String message) {

    }

    /**
     *
     * @param session
     * @param message
     */
    public static void addMsg(HttpSession session, String message) {

    }

    public static String consumeMsg(HttpServletRequest request) {
        return null;
    }

    public static String consumeMsg(HttpSession session) {
        return null;
    }

}
